"use strict";

var playFieldWidth = 10;
var playFieldHeight = 10;
var brickHeight = 0.3;

var worldMatrix = transpose(
    [[32,  0,  0], 
     [0,   0,  1],
     [0, -32,  0]]);

var worldTrans = affine(worldMatrix, [20, 260, 0]);

worldTrans = affineCompose(worldTrans, affine(rotationX(0.25 * Math.PI), [0, 0, 0]));
worldTrans = affineCompose(worldTrans, affine(rotationZ(-0.2 * Math.PI), [0, 0, 0]));

// Unit vectors
var unitX = [1, 0, 0];
var unitY = [0, 1, 0];
var unitZ = [0, 0, 1];

// Colors
var roboBlue = "aaf";
var stateToColor = {
    "OK" : "ff0",
    "WARN" : "fa0",
    "ERROR" : "f00",
    "FINISHED" : "0f0"
};

function formatCell(cell) {
    var m = cell.numMarks ? "*" : " ";
    var w = (cell.numBricks === 0) ? "_" : cell.numBricks; 
    return m + w;
}

function calcBase(x, y, z) {
    var v = affineTransform(worldTrans, [x, y, z]);
    return v;
}

function drawPoly(ctx, points) {
    ctx.beginPath();
    ctx.moveTo(points[0][0], points[0][1]);
    for (var i = 1; i < points.length; i++) {
        ctx.lineTo(points[i][0], points[i][1]);
    }
    ctx.lineTo(points[0][0], points[0][1]);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();
}

function drawRectVec(ctx, base, v1, v2) {
    var p0 = base;
    var p1 = vectorAdd(p0, v1);
    var p2 = vectorAdd(p1, v2);
    var p3 = vectorAdd(p0, v2);
    drawPoly(ctx, [p0, p1, p2, p3]);
}

function drawRectVecTrans(ctx, trans, base, v1, v2) {
    drawRectVec(ctx, affineTransform(trans, base), transformDir(trans, v1), transformDir(trans, v2));
}

function drawTop(ctx, x, y, z) {
    ctx.fillStyle = "#eee";
    drawRectVecTrans(ctx, worldTrans, [x, y, z], unitX, unitY);
}

function drawFront(ctx, x, y, z) {
    ctx.fillStyle = "#ddd";
    drawRectVecTrans(ctx, worldTrans, [x, y, z], unitX, scalarProduct(brickHeight, unitZ));
}

function drawRight(ctx, x, y, z) {
    ctx.fillStyle = "#ccc";
    drawRectVecTrans(ctx, worldTrans, vectorAdd([x, y, z], unitX), unitY, scalarProduct(brickHeight, unitZ));
}

function drawCube(ctx, trans, base, x, y, z) {
    // TODO - do this using matrices
    var tr = transpose(trans.matrix);
    var vecX = scalarProduct(x, tr[0]);
    var vecY = scalarProduct(y, tr[1]);
    var vecZ = scalarProduct(z, tr[2]);

    var base = affineTransform(trans, base);

    // Is the left side facing towards us?
    if (tr[0][2] > 0) { // Yes -> draw it
        drawRectVec(ctx, base, vecY, vecZ);
    }
    else { // No -> draw the right side
        drawRectVec(ctx, vectorAdd(base, vecX), vecY, vecZ);
    }
    if (tr[1][2] > 0) {
        drawRectVec(ctx, base, vecX, vecZ);
    }
    else {
        drawRectVec(ctx, vectorAdd(base, vecY), vecX, vecZ);
    }
    if (tr[2][2] > 0) {
        drawRectVec(ctx, base, vecX, vecY);
    }
    else {
        drawRectVec(ctx, vectorAdd(base, vecZ), vecX, vecY);
    }
}

function drawRobot(ctx, x, y, z, dir, state) {
    ctx.fillStyle = "#aaf";
    var t = affineCompose(worldTrans, affine(rotationZ(-dir / 2 * Math.PI), [x + 0.5, y + 0.5, z]));
    var xt = transformDir(t, unitX);
    var yt = transformDir(t, unitY);

    // Draw legs
    var drawLeg = function(x) {
        drawCube(ctx, t, [x - 0.1,  -0.1,  0],  0.2, 0.4, 0.1);
        drawCube(ctx, t, [x - 0.05, -0.05, 0.1], 0.1, 0.1, 0.9);
    }
    drawLeg(xt[2] > 0  ? 0.2 : - 0.2);
    drawLeg(xt[2] <= 0 ? 0.2 : - 0.2);

    // Draw the further arm
    drawCube(ctx, t, [xt[2] > 0 ? 0.3 : -0.4,  -0.1,  1],  0.1, 0.2, 0.9);
    // Draw torso
    drawCube(ctx, t, [-0.3,  -0.2,  1],  0.6, 0.4, 1);
    // Draw the closer arm
    drawCube(ctx, t, [xt[2] <= 0 ? 0.3 : -0.4,  -0.1,  1],  0.1, 0.2, 0.9);
    // Neck
    drawCube(ctx, t, [-0.1,  -0.1,  2],  0.2, 0.2, 0.1);
    // Head, eyes and mouth
    if (yt[2] <= 0) {
        drawCube(ctx, t, [-0.2,  -0.15,  2.1],  0.4, 0.3, 0.5);
    }
    drawCube(ctx, t, [0.08,  0.15,  2.4],  0.04, 0.02, 0.04);  // Eye1
    drawCube(ctx, t, [-0.12,  0.15,  2.4],  0.04, 0.02, 0.04); // Eye2
    drawCube(ctx, t, [-0.1,  0.15,  2.22],  0.2, 0.02, 0.04); // Eye2
    if (yt[2] > 0) {
        drawCube(ctx, t, [-0.2,  -0.15,  2.1],  0.4, 0.3, 0.5);
    }

    // Signal
    ctx.fillStyle = stateToColor[state];
    drawCube(ctx, t, [ -0.1, -0.1, 2.6], 0.2, 0.2, 0.15);
}

function drawMarks(ctx, trans, base, marks) {
    if (marks !== 0) {
        // OPT (could do with transpose)
        var uX = transformDir(trans, unitX);
        var uY = transformDir(trans, unitY);
        
        var paddingOffset = scalarProduct(0.1, vectorAdd(uX, uY));
        var sizeX = scalarProduct(0.2, uX);
        var sizeY = scalarProduct(0.2, uY);
        var stepX = scalarProduct(0.3, uX);
        var stepY = scalarProduct(0.3, uY);

        var corner = vectorAdd(affineTransform(trans, base), paddingOffset);

        var cX = 0;
        var cY = 0;
        ctx.beginPath();
        for (var i = 0; i < marks; i++) {
            var p0 = vectorAdd(corner, vectorAdd(scalarProduct(cX, stepX), scalarProduct(cY, stepY)));
            var p1 = vectorAdd(p0, sizeX);
            var p2 = vectorAdd(p1, sizeY);
            var p3 = vectorAdd(p0, sizeY);
            ctx.moveTo(p0[0], p0[1]);
            ctx.lineTo(p2[0], p2[1]);
            ctx.moveTo(p1[0], p1[1]);
            ctx.lineTo(p3[0], p3[1]);
            cX++;
            if (cX === 3) {
                cX = 0;
                cY++;
            }
        }
        ctx.stroke();

        var corner = vectorAdd(base, scalarProduct(0.1, vectorAdd(unitX, unitY)));
        var cornerT = affineTransform(trans, corner);
        var stepX = transformDir(trans, scalarProduct(0.3, unitX));
        var stepY = transformDir(trans, scalarProduct(0.3, unitY));
        var sizeX = transformDir(trans, scalarProduct(0.3, unitX));
        var stepX = transformDir(trans, scalarProduct(0.3, unitX));

        var ux = transformDir(trans, unitX);
        var uy = transformDir(trans, unitY);
    }
}

function drawField(ctx, field, karelPos) {
    for (var y = playFieldHeight - 1; y >= 0; y--) {
        for (var x = 0; x < playFieldWidth; x++) {
            var myWall = field[y][x].numBricks;
            drawTop(ctx, x, y, myWall * brickHeight);
            drawMarks(ctx, worldTrans, [x, y, myWall * brickHeight], field[y][x].numMarks); 
            var frontWall = (y === 0) ? 0 : field[y - 1][x].numBricks;
            for (var k = frontWall; k < myWall; k++) {
                drawFront(ctx, x, y, k * brickHeight);
            }
            var rightWall = (x === playFieldWidth - 1) ? 0 : field[y][x + 1].numBricks;
            for (var k = rightWall; k < myWall; k++) {
                drawRight(ctx, x, y, k * brickHeight);
            }
            if (karelPos.x === x && karelPos.y === y) {
                drawRobot(ctx, karelPos.x, karelPos.y, field[karelPos.y][karelPos.x].numBricks * brickHeight, karelPos.dir, karelPos.state);
            }
        }
    }
}

var program1 = [
    { kind : "REPEAT", count : 1, body : [{ kind : "STEP" }] },
    { kind : "TURNLEFT" } ];

var program2 = [
    { kind : "WHILE", condition : { kind : "FREE" }, body : [{kind : "STEP"}] },
    { kind : "STEP" },
    { kind : "TURNLEFT" },
    { kind : "STEP" },
    { kind : "STEP" },
    { kind : "PUT_BRICK" },
    { kind : "PUT_BRICK" },
    { kind : "PUT_BRICK" },
    { kind : "TAKE_BRICK" },
    { kind : "REPEAT", count : 4, body : [{ kind : "PUT_MARK" }] },
    { kind : "TAKE_MARK" },
    { kind : "TURNLEFT" },
    { kind : "STEP" },
    { kind : "REPEAT", count : 4, body : [{ kind : "STEP" }] },
    { kind : "PUT_BRICK" },
    { kind : "PUT_BRICK" },
    { kind : "PUT_BRICK" },
    { kind : "TURNLEFT" },
    { kind : "STEP" },
    { kind : "STEP" },
    { kind : "TURNLEFT" } ];

function erasePlayField(fieldState) {
    var field = fieldState.field;

    for (var i = 0; i < field.length; i++) {
        for (var j = 0; j < field[i].length; j++) {
            field[i][j].numMarks = 0, 
            field[i][j].numBricks = 0 ;
        }
    }

    fieldState.karelPos.x = 0; 
    fieldState.karelPos.y = 0; 
    fieldState.karelPos.dir = 0;
    fieldState.karelPos.state = "OK";
}

function main() {
    myTest();

    var playfield = [];
    for (var i = 0; i < playFieldHeight; i++) {
        playfield[i] = []; 
        for (var j = 0; j < playFieldWidth; j++) {
            playfield[i][j] = { numMarks : 0, 
                                numBricks : 0 };
        }
    }

    var fieldState = { karelPos : { x : 4, y : 4, dir : 2}, field : playfield };
    var evalStack = [ { current : 0, statements : program1 } ];
    var programEnv = { };

    var $runButton = document.getElementById("runButton");

    $runButton.onclick = function() { 
        erasePlayField(fieldState);
        var prg = document.getElementById("code").value;
        console.log(prg);
        var parsedPrg = parseProgram(prg);
        evalStack = [ { current : 0, statements : parsedPrg.body } ];
        programEnv = parsedPrg.defs;
    };
     
    var myf = function() {
        var canvas = document.getElementById('field');
        var ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height); 
        ctx.strokeStyle = "#000";
        ctx.setLineWidth(.5);

        drawField(ctx, fieldState.field, fieldState.karelPos);
        var pre = document.getElementById('dbg');
        pre.innerText = outField(fieldState.field);

        fieldState.karelPos.state = evalStep(evalStack, programEnv, fieldState); 
    }

    setInterval(myf, 250);
    myf();
    myf();
}

function outField(playfield) {
    var txt = "";
    for (var i = playFieldHeight - 1; i >= 0; i--) {
        for (var j = 0; j < playFieldWidth; j++) {
            txt += formatCell(playfield[i][j]);
        }
        txt += "\n";
    }
    return txt;
}


